Skip to content

feat(auth): add backup restore manager#76

Closed
ndycode wants to merge 50 commits intogit-plan/01-reset-safetyfrom
git-plan/02-backup-restore-manager
Closed

feat(auth): add backup restore manager#76
ndycode wants to merge 50 commits intogit-plan/01-reset-safetyfrom
git-plan/02-backup-restore-manager

Conversation

@ndycode
Copy link
Owner

@ndycode ndycode commented Mar 12, 2026

What

  • add a named-backup restore manager to codex auth login and a direct codex auth restore-backup entrypoint
  • harden named-backup containment, retry behavior, direct-command scope reset, and restore-time error recovery
  • cover empty-assessment failure handling, read-time ENOENT mapping, metadata-refresh, direct-command exit codes, and Windows lock edge cases with targeted vitest regressions

Why

  • restore needed to work when storage is empty or stale, without letting backup paths escape the backups directory
  • restore-time failures needed to stay inside the interactive recovery flow instead of unwinding the CLI
  • the direct restore command needed to clear stale scoped storage and return a non-zero exit code when restore work actually fails

Risk

Low to medium. Scope stays inside the backup restore flow, storage containment/retry logic, CLI status handling, and targeted tests.

Rollback

Revert the follow-up commits on git-plan/02-backup-restore-manager if backup restore behavior regresses.

Validation

  • npx eslint lib/storage.ts lib/codex-manager.ts test/storage.test.ts test/codex-manager-cli.test.ts
  • npx vitest run test/storage.test.ts test/codex-manager-cli.test.ts --maxWorkers=1
  • npm run typecheck blocked in this worktree because @codex-ai/plugin, @codex-ai/plugin/tool, and @codex-ai/sdk are not installed here
  • npm test blocked for the same missing-package reason in this worktree

Docs

  • docs/reference/commands.md
  • docs/reference/storage-paths.md
  • docs/reference/public-api.md

Governance

  • follow-up fixes stayed scoped to backup restore review feedback
  • no dist/ changes
  • latest follow-up commit on this PR: ef05872

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

this pr adds named-backup restore support: new storage APIs (create/list/assess/restore), cli/ui wiring for a restore-backup login mode, a backup-restore manager in lib/codex-manager.ts, and broad tests covering listing, assessment, and restore flows.

Changes

Cohort / File(s) Summary
Documentation
docs/reference/storage-paths.md
added canonical and project-scoped named backup paths and a restore workflow entry.
CLI
lib/cli.ts
added "restore-backup" to LoginMode and mapped fallback keys (u, backup, restore) to it (lib/cli.ts:line ~1).
Storage layer
lib/storage.ts
introduced named-backup public APIs/types (NamedBackupMetadata, BackupRestoreAssessment, ImportAccountsResult, NAMED_BACKUP_LIST_CONCURRENCY, NAMED_BACKUP_ASSESS_CONCURRENCY, listNamedBackups(), getNamedBackupsDirectoryPath(), createNamedBackup(), assessNamedBackupRestore(), restoreNamedBackup()), concurrency-aware listing/assessment, transient-retry wrappers, path-containment guards, and storagePath propagation through transactions (lib/storage.ts:line ~1-800).
Manager / orchestration
lib/codex-manager.ts
added BackupMenuAction type and runBackupRestoreManager(displaySettings); manager lists backups, batches assessments (concurrency-limited), re-assesses on selection, prompts confirm, and performs restore; integrated into auth/login flow and exposes codex auth restore-backup (lib/codex-manager.ts:line ~1-500).
UI / copy
lib/ui/auth-menu.ts, lib/ui/copy.ts
added restore-backup action and menu item under a recovery section; added copy entries, confirmation prompts, success/refresh messages, and updated fallback prompts to include (u) restore backup (lib/ui/auth-menu.ts:line ~1, lib/ui/copy.ts:line ~1).
Tests
test/codex-manager-cli.test.ts, test/storage.test.ts, test/cli.test.ts
added extensive tests and mocks for listing/assessing/restoring named backups, confirmation prompt behavior, concurrency and re-assessment, fallback alias mapping, and many edge cases (test/codex-manager-cli.test.ts:line ~1-1800, test/storage.test.ts:line ~1-2200, test/cli.test.ts:line ~1-40).

Sequence diagram

sequenceDiagram
    participant user as User
    participant auth as Auth Menu
    participant mgr as BackupRestoreManager
    participant storage as Storage
    participant fs as File System

    rect rgba(200,200,255,0.5)
    user->>auth: select "restore from backup"
    auth->>mgr: runBackupRestoreManager()
    end

    rect rgba(200,255,200,0.5)
    mgr->>storage: listNamedBackups()
    storage->>fs: read backups dir
    fs-->>storage: backup entries
    storage-->>mgr: NamedBackupMetadata[]
    end

    rect rgba(255,230,200,0.5)
    mgr->>storage: assessNamedBackupRestore(name) (batched)
    storage->>fs: read backup file(s)
    fs-->>storage: backup content
    storage-->>mgr: BackupRestoreAssessment[]
    mgr->>user: display menu with hints
    user->>mgr: choose backup
    mgr->>storage: reassessNamedBackupRestore(name)
    mgr->>user: prompt confirm()
    user->>mgr: confirm
    mgr->>storage: restoreNamedBackup(name)
    storage->>fs: merge/import accounts
    fs-->>storage: write results
    storage-->>mgr: {imported, skipped, total, changed}
    mgr->>user: show result / error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

reviewable concerns

  • missing regression tests for partially corrupted backups mid-restore. add tests covering partial corruption and aborted/partial imports (lib/storage.ts:line ~420, test/storage.test.ts:line ~800).
  • concurrency risk: restore and export/merge write paths lack cross-process locking and can race; consider file locks or atomic-replace strategies (lib/storage.ts:line ~320, lib/codex-manager.ts:line ~420).
  • windows path escaping and normalization edge cases. add windows-style path tests for findExistingNamedBackupPath and containment helpers (lib/storage.ts:line ~120, test/storage.test.ts:line ~220).
  • missing tests for concurrent restore attempts of the same named backup. add parallel-restore tests to validate retry/guarding behavior (lib/storage.ts:line ~350, test/storage.test.ts:line ~1200).
  • ui confirm robustness in non-tty contexts. add tests for confirm timeouts and non-interactive environments to ensure deterministic fallback behavior (lib/codex-manager.ts:line ~300, test/codex-manager-cli.test.ts:line ~60).
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed title follows conventional commits format with feat type, lowercase scope, imperative summary under 72 chars, and directly describes the main change: adding a backup restore manager to auth flow.
Description check ✅ Passed PR description covers what changed, why, risks, rollback, validation, and docs; however, typecheck and full test suite validation are blocked by missing dependencies in the worktree.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch git-plan/02-backup-restore-manager
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch git-plan/02-backup-restore-manager
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/storage.test.ts">

<violation number="1" location="test/storage.test.ts:316">
P2: Use retry-based cleanup instead of bare `fs.rm` in test teardown to avoid Windows EBUSY/EPERM/ENOTEMPTY flakiness.</violation>
</file>

<file name="lib/storage.ts">

<violation number="1" location="lib/storage.ts:475">
P2: Backup name normalization in path resolution breaks restore for listed backup files whose filenames contain spaces or other non-normalized characters.</violation>
</file>

<file name="test/codex-manager-cli.test.ts">

<violation number="1" location="test/codex-manager-cli.test.ts:1609">
P3: `vi.doMock` here is never unmocked. `vi.resetModules()` does not clear the mock registry, so this confirm mock will leak into later tests and can change their behavior. Add a `vi.doUnmock`/`vi.unmock` cleanup (e.g., in `afterEach`) to keep tests isolated.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/storage.test.ts">

<violation number="1" location="test/storage.test.ts:132">
P2: Replace the bare `fs.rm` in test cleanup with retry/backoff to avoid Windows CI flakes on transient file locks.</violation>
</file>

<file name="lib/storage.ts">

<violation number="1" location="lib/storage.ts:476">
P2: `resolveNamedBackupPath` allows path traversal via unsanitized backup names before directory confinement.</violation>
</file>

<file name="lib/cli.ts">

<violation number="1" location="lib/cli.ts:283">
P2: `restore-backup` is wired only in the TUI path; the fallback login prompt cannot select this new mode.</violation>
</file>

<file name="lib/codex-manager.ts">

<violation number="1" location="lib/codex-manager.ts:4171">
P2: Handle restoreNamedBackup errors so a missing/invalid backup or limit breach doesn’t crash the auth menu.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/cli.ts (1)

49-61: ⚠️ Potential issue | 🟠 Major

the new mode is still unreachable in the readline fallback menu.

this only wires restore-backup through the tty path. promptLoginModeFallback() in lib/cli.ts:174-243 and its prompt copy in lib/ui/copy.ts:135-137 still never advertise or parse the mode, so users in fallback environments cannot reach the restore flow even though runAuthLogin() handles it at lib/codex-manager.ts:3909-3919. please add a vitest for the fallback branch.

As per coding guidelines, lib/**: focus on auth rotation, windows filesystem io, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios.

Also applies to: 291-292

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/cli.ts` around lines 49 - 61, The fallback readline menu never exposes or
parses the new "restore-backup" LoginMode, so add it to
promptLoginModeFallback() (ensure the option label and value include
"restore-backup") and update the fallback prompt text in lib/ui/copy.ts to
advertise the restore flow (matching how runAuthLogin() in lib/codex-manager.ts
handles "restore-backup"); add a vitest that exercises the fallback branch to
assert the prompt lists and correctly parses "restore-backup", and update any
affected tests to reference the new behavior; while modifying prompts/tests,
verify any updated auth rotation or queue logic touched by this change properly
retries or surfaces EBUSY/429 errors per existing concurrency and Windows IO
guidelines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/reference/storage-paths.md`:
- Line 25: Update docs/reference/storage-paths.md to document the restore entry
point and user workflow for the new backup paths: add a short step-by-step
showing "codex auth login" → select recovery → choose 'restore from backup' and
point readers to the interactive restore implementation in lib/codex-manager.ts
(see the interactive flow around the blocks at ~3909-3919 and the restore logic
at ~4211-4291) so users know how to consume files under
~/.codex/multi-auth/backups/<name>.json; also add or link an upgrade note that
mentions the changed CLI workflow and any new npm scripts introduced so README,
SECURITY, and docs remain consistent with the shipped flags and behavior.

In `@lib/codex-manager.ts`:
- Around line 3909-3920: The recovery menu (restore-backup) is never reachable
because runAuthLogin() only enters the menu loop when
existingStorage.accounts.length > 0; hoist the recovery entry so
runBackupRestoreManager(...) can be invoked even when existingStorage.accounts
is empty: move the restore-backup handling (call to runBackupRestoreManager and
its try/catch) to execute before the non-empty accounts guard in runAuthLogin,
ensuring the menu can present the recovery path on fresh/reset/missing storage;
add a vitest that simulates zero-account storage and asserts the restore flow is
offered and that runBackupRestoreManager is called; update any affected tests
referenced in the auth/login suite and ensure new queue logic (if added) handles
EBUSY and 429 retries per concurrency guidelines.

In `@lib/storage.ts`:
- Around line 1712-1728: loadBackupCandidate currently converts any read error
into a permanent invalid-backup result; change it to detect transient Windows/IO
errors (EPERM, EBUSY, EAGAIN) and retry the read (call to loadAccountsFromPath)
with an exponential/backoff loop (same retry envelope used elsewhere in this
module) before returning an error, and apply the same retry logic to the paired
fs.stat call in this file that checks backup file metadata; add a vitest that
simulates a transient busy/EPERM/EBUSY/EAGAIN failure on the first N attempts
and succeeds thereafter to assert the retry path is exercised and cite/update
the affected tests.
- Around line 1580-1609: The function listNamedBackups currently swallows all
readdir errors except ENOENT by logging and returning an empty array; change the
catch in listNamedBackups so that only ENOENT returns [] while any other error
is re-thrown (preserve the original error object) so callers like
runBackupRestoreManager surface real filesystem errors (EPERM/EBUSY). Update
tests: add a vitest that simulates an unreadable backup directory (make readdir
throw EPERM/EBUSY) and assert that listNamedBackups rejects/throws rather than
returning an empty array. Keep references to getNamedBackupRoot/getStoragePath
for locating the path and ensure loadBackupCandidate/buildNamedBackupMetadata
logic is untouched.

In `@test/codex-manager-cli.test.ts`:
- Around line 2365-2428: Add a regression test that covers the confirm=false
path so restore is blocked: duplicate the existing "restores a named backup..."
test setup (use setInteractiveTTY, mock loadAccountsMock, listNamedBackupsMock,
assessNamedBackupRestoreMock, promptLoginModeMock and selectMock) but make
confirmMock resolve to false and then call
runCodexMultiAuthCli(["auth","login"]); assert exitCode is non-zero (or
indicates cancellation) and that restoreNamedBackupMock was not called; keep
other expectations similar (list/assess called once, select used) to ensure the
safety gate implemented by confirmMock is exercised.

In `@test/storage.test.ts`:
- Around line 1111-1185: The tests cover happy paths but lack regression cases
that ensure restoreNamedBackup and assessNamedBackupRestore properly surface
failures for invalid or missing backup payloads; add deterministic vitest tests
in test/storage.test.ts that (1) create a named backup file with invalid JSON
and assert assessNamedBackupRestore(...) and restoreNamedBackup(...) both
reject/throw, (2) create a backup JSON missing required fields (e.g., no
"accounts") and assert both functions reject with a clear error, and (3) assert
behavior is consistent when a backup file is empty or truncated; use the same
helper functions (createNamedBackup, listNamedBackups, assessNamedBackupRestore,
restoreNamedBackup, clearAccounts, loadAccounts) and filesystem writes to inject
the malformed payloads so the tests reproduce the error-path regressions
deterministically under vitest.

---

Outside diff comments:
In `@lib/cli.ts`:
- Around line 49-61: The fallback readline menu never exposes or parses the new
"restore-backup" LoginMode, so add it to promptLoginModeFallback() (ensure the
option label and value include "restore-backup") and update the fallback prompt
text in lib/ui/copy.ts to advertise the restore flow (matching how
runAuthLogin() in lib/codex-manager.ts handles "restore-backup"); add a vitest
that exercises the fallback branch to assert the prompt lists and correctly
parses "restore-backup", and update any affected tests to reference the new
behavior; while modifying prompts/tests, verify any updated auth rotation or
queue logic touched by this change properly retries or surfaces EBUSY/429 errors
per existing concurrency and Windows IO guidelines.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f8fa6ad5-4c6c-4110-af7b-d6c8d1c2f62c

📥 Commits

Reviewing files that changed from the base of the PR and between d67ca3d and 7443074.

📒 Files selected for processing (8)
  • docs/reference/storage-paths.md
  • lib/cli.ts
  • lib/codex-manager.ts
  • lib/storage.ts
  • lib/ui/auth-menu.ts
  • lib/ui/copy.ts
  • test/codex-manager-cli.test.ts
  • test/storage.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
lib/**

⚙️ CodeRabbit configuration file

focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.

Files:

  • lib/cli.ts
  • lib/ui/auth-menu.ts
  • lib/codex-manager.ts
  • lib/ui/copy.ts
  • lib/storage.ts
docs/**

⚙️ CodeRabbit configuration file

keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.

Files:

  • docs/reference/storage-paths.md
test/**

⚙️ CodeRabbit configuration file

tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.

Files:

  • test/storage.test.ts
  • test/codex-manager-cli.test.ts
🔇 Additional comments (3)
test/storage.test.ts (1)

333-336: good windows-safe teardown change.

line 335 in test/storage.test.ts:335 uses removeWithRetry, which reduces eperm/ebusy teardown flakes on windows.

test/codex-manager-cli.test.ts (2)

506-539: good deterministic mock reset/default setup.

line 506 through line 539 in test/codex-manager-cli.test.ts correctly resets and reseeds new restore-related mocks, which prevents cross-test state bleed.


2430-2500: good restore-failure recovery coverage.

line 2430 through line 2500 in test/codex-manager-cli.test.ts verifies restore failure is surfaced and the login flow continues, which is the right user-safety behavior.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/storage.test.ts">

<violation number="1" location="test/storage.test.ts:1208">
P3: Duplicate test case: this restore-after-deletion scenario is already covered earlier in the same file.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@coderabbitai full review

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@greptileai

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

✅ Actions performed

Full review triggered.

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@cubic-dev-ai review this PR

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 14, 2026

@cubic-dev-ai review this PR

@ndycode I have started the AI code review. It will take a few minutes to complete.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/storage.test.ts">

<violation number="1" location="test/storage.test.ts:1351">
P2: Use `removeWithRetry` instead of bare `fs.rm` to avoid transient Windows file-lock failures in tests.</violation>
</file>

<file name="lib/storage.ts">

<violation number="1" location="lib/storage.ts:1807">
P2: Do not swallow non-ENOENT errors when scanning named backups; rethrow them so restore reports the real filesystem failure instead of a misleading “file not found.”</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@greptileai

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@cubic-dev-ai review this PR

@ndycode
Copy link
Owner Author

ndycode commented Mar 14, 2026

@coderabbitai full review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 14, 2026

@cubic-dev-ai review this PR

@ndycode I have started the AI code review. It will take a few minutes to complete.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

@ndycode — triggering a full review of pr #76 now. scope will cover:

  • lib/storage.ts — triple-negation in findExistingNamedBackupPath, swallowed validation error in resolveNamedBackupRestorePath catch block (replaces real errors with "import file not found"), NAMED_BACKUP_LIST_CONCURRENCY bounding, symlink rejection, toctou re-assessment guards, retryTransientFilesystemOperation eperm/ebusy/eagain coverage, and the storagePath transaction guard in exportAccounts
  • lib/codex-manager.tsrunBackupRestoreManager fan-out concurrency, re-assessment-before-confirm flow, error containment back to login menu, formatRelativeDateShort edge cases (midnight rollover, locale), and the refactored runAuthLogin loop
  • lib/cli.tsrestore-backup mode variant and fallback alias wiring for "u"/"backup"/"restore"
  • lib/ui/auth-menu.ts / lib/ui/copy.ts — recovery section ui injection and copy string additions
  • test/storage.test.ts / test/codex-manager-cli.test.ts — concurrency regression, windows eperm retry, uppercase .JSON restore, toctou delete-after-assessment, partial assessment failure, symlink rejection, re-assessment freshness, and zero-account recovery paths
✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
test/codex-manager-cli.test.ts (1)

3468-3506: ⚠️ Potential issue | 🟠 Major

epoch hint regression is asserted in the wrong direction.

the test name says epoch timestamps are shown, but the assertion expects "updated " to be absent. this weakens the epoch-rendering contract and can hide regressions. align either the assertion or the test name. test/codex-manager-cli.test.ts:3468-3506, lib/codex-manager.ts:4258.

minimal fix if epoch should be rendered
- expect(backupItems?.[0]?.hint).not.toContain("updated ");
+ expect(backupItems?.[0]?.hint).toContain("updated ");

As per coding guidelines, test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/codex-manager-cli.test.ts` around lines 3468 - 3506, The test "shows
epoch backup timestamps in restore hints" currently asserts the opposite by
checking expect(backupItems?.[0]?.hint).not.toContain("updated "), so update the
test assertion to match the test name/intent: change that line to
expect(backupItems?.[0]?.hint).toContain("updated ") (or otherwise assert the
epoch-rendered timestamp substring you want) in the spec that calls
runCodexMultiAuthCli and inspects selectMock.mock.calls
(test/codex-manager-cli.test.ts), so the test verifies the epoch timestamp is
rendered when assessNamedBackupRestoreMock returns an assessment; keep the rest
of the setup (listNamedBackupsMock, promptLoginModeMock, selectMock) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/codex-manager.ts`:
- Around line 4361-4365: Replace the direct call to
resolveNamedBackupRestorePath + importAccounts (the block using
latestAssessment.backup.name, resolveNamedBackupRestorePath, and importAccounts)
with the storage-level named-backup restore helper used earlier in the confirm
flow (i.e., call the same storage restore wrapper that produces a safe,
exclusive restore path instead of resolving and opening the file directly), so
the final import uses the storage restore path and its concurrency/lock
handling; ensure the new path handles EBUSY/EPERM and HTTP 429 retry semantics,
preserves auth-rotation (do not leak tokens/emails in logs), and add two vitest
regressions: one in test/storage.test.ts and one in
test/codex-manager-cli.test.ts that simulate "backup removed or locked after
confirm" to assert the code retries/fails safely and logs non-sensitive
diagnostics.
- Around line 4222-4224: Add a direct dispatch for the new restore-backup CLI
command and a unit test: in the runCodexMultiAuthCli function add a branch that
recognizes the ["auth","restore-backup"] args and calls
runBackupRestoreManager(displaySettings) (preserving existing interactive flow),
update printUsage to include the "auth restore-backup" entry so it appears in
help output, and add a vitest in test/codex-manager-cli.test.ts that calls
runCodexMultiAuthCli(["auth","restore-backup"]) (mock or spy
runBackupRestoreManager as needed) to assert the new path is exercised.

In `@lib/storage.ts`:
- Around line 2137-2169: Add a vitest in test/storage.test.ts that exercises
resolveNamedBackupRestorePath's fallback branch by simulating a plain filename
that causes buildNamedBackupPath to throw while findExistingNamedBackupPath
returns null; specifically, call resolveNamedBackupRestorePath with a name that
is a basename (no separators, no "..") whose baseName does not match
/^[A-Za-z0-9_-]+$/ so the catch path executes and the function should throw an
Error whose message equals the “Import file not found: …” string produced by
resolvePath(join(backupRoot, requestedWithExtension)). Mock or stub
buildNamedBackupPath to throw, stub findExistingNamedBackupPath to return null,
and ensure getNamedBackupRoot/getStoragePath produce a stable known backupRoot
(account for Windows path sep) and assert the thrown error message matches the
exact resolved path; add the test as a regression for race/lock scenarios around
resolveNamedBackupRestorePath, buildNamedBackupPath, and
assertNamedBackupRestorePath.

In `@test/codex-manager-cli.test.ts`:
- Around line 2659-2732: Add a deterministic regression test that simulates the
backup disappearing after selection by making
resolveNamedBackupRestorePathMock.reject with an ENOENT error (e.g., new Error
with code "ENOENT") when the user selects the named backup; then assert the CLI
returns to the login menu (promptLoginModeMock called again), that
importAccountsMock and assessNamedBackupRestoreMock were not called for the
final resolve, and that a restore-failure message containing "ENOENT" or
"Restore failed" was logged; use the existing test scaffolding (selectMock,
promptLoginModeMock, runCodexMultiAuthCli) and vitest mocks so the test remains
deterministic.

In `@test/storage.test.ts`:
- Around line 2430-2448: The test asserts readdirSpy was called 7 times (1
initial + 6 retries) which is unclear; update the assertion to derive the
expected call count from the retry policy (e.g., use the
retryTransientFilesystemOperation retry constant or export a MAX_RETRIES value)
or add a one-line comment above the expectation explaining "1 initial attempt +
N retries" so readers can see why 7 is expected; locate the assertion around
restoreNamedBackup and readdirSpy in the test and either compute expectedCalls =
1 + MAX_RETRIES for the toHaveBeenCalledTimes check or add the clarifying
comment referencing retryTransientFilesystemOperation.

---

Duplicate comments:
In `@test/codex-manager-cli.test.ts`:
- Around line 3468-3506: The test "shows epoch backup timestamps in restore
hints" currently asserts the opposite by checking
expect(backupItems?.[0]?.hint).not.toContain("updated "), so update the test
assertion to match the test name/intent: change that line to
expect(backupItems?.[0]?.hint).toContain("updated ") (or otherwise assert the
epoch-rendered timestamp substring you want) in the spec that calls
runCodexMultiAuthCli and inspects selectMock.mock.calls
(test/codex-manager-cli.test.ts), so the test verifies the epoch timestamp is
rendered when assessNamedBackupRestoreMock returns an assessment; keep the rest
of the setup (listNamedBackupsMock, promptLoginModeMock, selectMock) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c8e11074-2d65-440a-bea3-4661b061dd6c

📥 Commits

Reviewing files that changed from the base of the PR and between d67ca3d and 8d61d29.

📒 Files selected for processing (9)
  • docs/reference/storage-paths.md
  • lib/cli.ts
  • lib/codex-manager.ts
  • lib/storage.ts
  • lib/ui/auth-menu.ts
  • lib/ui/copy.ts
  • test/cli.test.ts
  • test/codex-manager-cli.test.ts
  • test/storage.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Greptile Review
  • GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (3)
lib/**

⚙️ CodeRabbit configuration file

focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.

Files:

  • lib/cli.ts
  • lib/ui/copy.ts
  • lib/codex-manager.ts
  • lib/ui/auth-menu.ts
  • lib/storage.ts
test/**

⚙️ CodeRabbit configuration file

tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.

Files:

  • test/storage.test.ts
  • test/codex-manager-cli.test.ts
  • test/cli.test.ts
docs/**

⚙️ CodeRabbit configuration file

keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.

Files:

  • docs/reference/storage-paths.md
🔇 Additional comments (22)
test/cli.test.ts (1)

707-729: good restore alias coverage.

test/cli.test.ts:707-729 now locks all fallback aliases (u, restore, backup, restore-backup) to mode: "restore-backup". this closes the mapping gap cleanly.

lib/storage.ts (1)

1644-1762: retry envelope and bounded listing concurrency look solid.

lib/storage.ts:1644-1762 (line 1650 and line 1739) correctly combines retryable windows filesystem handling with bounded batch processing, and it preserves hard failures outside ENOENT.

lib/cli.ts (1)

60-60: restore-backup mode wiring is consistent across fallback and tty flows.

lib/cli.ts:237-244 and lib/cli.ts:299-300 map the same restore action in both input paths, and test/cli.test.ts:707-729 covers the fallback aliases.

Also applies to: 237-244, 299-300

docs/reference/storage-paths.md (1)

25-25: docs now describe the shipped restore entry point clearly.

docs/reference/storage-paths.md:105-111 and docs/reference/storage-paths.md:127 align with the interactive codex auth login recovery flow and named-backup paths.

Also applies to: 60-60, 105-111, 127-127

lib/ui/auth-menu.ts (1)

67-67: menu action and recovery section integration look correct.

lib/ui/auth-menu.ts:67, lib/ui/auth-menu.ts:521-522, and lib/ui/auth-menu.ts:660-670 stay type-safe and keep focus navigation consistent with the new restore action.

Also applies to: 521-522, 660-670

lib/ui/copy.ts (1)

17-37: restore copy changes are aligned with the new flow.

lib/ui/copy.ts:17-37 and lib/ui/copy.ts:155-156 add the recovery wording and fallback prompt updates needed for restore-backup without reintroducing the old refreshed-count ambiguity.

Also applies to: 155-156

test/storage.test.ts (11)

338-341: good use of removeWithRetry for windows compatibility in teardown.

the switch from fs.rm to removeWithRetry at test/storage.test.ts:340 addresses ebusy/eperm flakiness on windows during test cleanup. this follows the pattern established in test/helpers/remove-with-retry.ts and keeps the import/export test block deterministic.


475-511: scoped writefile spy assertions correctly filter by storage path.

the filtering logic at test/storage.test.ts:501-507 now checks target.startsWith(\${testStoragePath}.`)` to include wal/bak variants without catching unrelated writes. this addresses the previous concern about overly broad assertions and keeps the test deterministic.


1584-1593: fallback path for missing named backups now covered.

test/storage.test.ts:1584-1593 exercises resolveNamedBackupRestorePath when the backup file doesn't exist, verifying it returns a contained path via buildNamedBackupPath and that importAccounts correctly rejects with "import file not found". this addresses the greptile-flagged coverage gap for the windows concurrency edge case where a backup disappears between list and restore.


1985-2039: toctou guards with atomicity assertions look solid.

tests at test/storage.test.ts:1985-2011 and test/storage.test.ts:2013-2039 verify that when a named backup is deleted or corrupted after assessment, restoreNamedBackup throws and active storage remains untouched. the assertions expect((await loadAccounts())?.accounts ?? []).toHaveLength(0) pin restore atomicity as required.


2041-2051: path traversal error matching is now specific.

the regex at test/storage.test.ts:2045 and test/storage.test.ts:2048 uses /must not contain path separators/i which matches the actual error text from lib/storage.ts. this addresses the past review comment about overly broad alternations.


2547-2596: transient eperm retry path on win32 now covered.

test/storage.test.ts:2547-2596 mocks platform as win32 and injects a single eperm failure during listNamedBackups(), asserting the operation succeeds after retry. this addresses the past review comment about missing eperm retry coverage for the named-backup path.


3049-3101: concurrency workload scales with exported limit.

test/storage.test.ts:3051 uses NAMED_BACKUP_LIST_CONCURRENCY + 4 for total backup count, ensuring the test always exceeds the limiter threshold regardless of the configured concurrency value. the assertion at test/storage.test.ts:3095-3097 correctly references the constant instead of a magic number.


2730-2766: manual backup deletion atomicity regression is thorough.

test/storage.test.ts:2730-2766 captures storage state before restore, deletes the backup, and asserts both the throw and that loadAccounts() returns the unchanged pre-restore state. this pins restore atomicity for the manual-backup scenario as flagged in past review.


3103-3172: concurrent restore serialization test is well-designed.

test/storage.test.ts:3103-3172 creates a near-limit state with two eligible backups, then races restoreNamedBackup on both. asserting exactly one succeeds and one fails with "import would exceed maximum" verifies the transaction lock serializes restores correctly. good coverage for the concurrency bug regression case.


2115-2170: symlink/junction traversal hardening is pinned.

tests at test/storage.test.ts:2115-2170 create symlinks (junctions on win32) pointing outside the backup root and verify assertNamedBackupRestorePath throws "escapes backup directory". this covers the windows junction edge case where the selected name doesn't contain .. but resolves outside the root.


2336-2390: listing ignores symlink entries and assessment rejects them explicitly.

test/storage.test.ts:2336-2390 mocks a readdir entry with isSymbolicLink() => true, verifies listNamedBackups() returns empty, and asserts assessNamedBackupRestore / restoreNamedBackup throw "not a regular backup file". this closes the traversal hardening gap for symlinked entries inside the backups directory.

test/codex-manager-cli.test.ts (4)

517-556: good baseline reset for the new restore mocks.

this setup keeps restore-path tests isolated and deterministic across cases. test/codex-manager-cli.test.ts:517-556.


2592-2657: good safety-gate regression for confirm=false.

this explicitly locks the user-confirmation guard and verifies the login menu loop continues. test/codex-manager-cli.test.ts:2592-2657.


3022-3097: good bounded-concurrency regression for backup assessment.

the deferred gate plus maxInFlight assertion directly protects named_backup_assess_concurrency behavior. test/codex-manager-cli.test.ts:3022-3097.


4901-4989: good race-ordering check before restore menu work starts.

the invocation-order assertion clearly verifies quota refresh completion precedes backup listing. test/codex-manager-cli.test.ts:4901-4989.

lib/codex-manager.ts (1)

3829-3885: nice recovery loop and quota-refresh serialization.

loading currentStorage on every menu iteration at lib/codex-manager.ts:3830-3834 keeps the restore path reachable even when storage is empty, and awaiting pendingMenuQuotaRefresh before the quota-touching actions at lib/codex-manager.ts:3875-3885 avoids reintroducing the cache-writer race. good change. the new coverage in test/codex-manager-cli.test.ts:1 is the right place to keep this pinned.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 9 files

@ndycode ndycode requested a review from Copilot March 15, 2026 09:38
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a named-backup restore/recovery flow to codex auth login and a direct codex auth restore-backup command, including backup listing, assessment, and restore logic with containment/symlink guards and more robust import/restore behavior.

Changes:

  • Introduces named-backup listing/assessment/restore APIs in storage, including transient filesystem retry and containment validation.
  • Adds interactive “Recovery → Restore From Backup” menu + a restore-backup CLI entrypoint and confirmation flow.
  • Expands test coverage for imports (no-op/metadata refresh/dedup/limit) and backup restore manager behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
lib/storage.ts Implements named-backup metadata, listing, assessment, restore, containment checks, transient FS retries, and import no-op detection.
lib/codex-manager.ts Adds interactive restore manager flow and codex auth restore-backup command wiring.
lib/cli.ts Adds restore-backup login mode + fallback aliases.
lib/ui/auth-menu.ts Adds “Recovery” section and “Restore From Backup” action to the auth menu.
lib/ui/copy.ts Adds UI copy for restore confirmations and success messaging; updates fallback prompt text.
docs/reference/storage-paths.md Documents named backup paths and the restore workflow.
test/storage.test.ts Adds extensive coverage for import no-op behavior, named backups, containment guards, and transient FS handling.
test/codex-manager-cli.test.ts Adds restore-backup CLI/menu tests + error-handling and concurrency/ordering expectations.
test/cli.test.ts Tests restore-backup fallback alias inputs.
Comments suppressed due to low confidence (1)

lib/storage.ts:3108

  • importAccounts()now persists a deduplicated account list but keepsactiveIndexunchanged. If the existing storage contained duplicates andexistingActiveIndexpointed to an entry that is removed by deduplication,activeIndexcan become out-of-range in the persisted file. Consider clampingactiveIndex(and any per-family active index fields) to[0, accounts.length - 1]` after deduplication—ideally via a shared helper used elsewhere for index normalization.
		const newStorage: AccountStorageV3 = {
			version: 3,
			accounts: deduplicatedAccounts,
			activeIndex: existingActiveIndex,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

export async function importAccounts(
filePath: string,
): Promise<{ imported: number; total: number; skipped: number }> {
): Promise<ImportAccountsResult> {
Comment on lines +1943 to +1947
lstat(path: string) {
return lstatSync(path);
},
realpath(path: string) {
return realpathSync.native(path);
Comment on lines +4234 to +4238
console.error(
`Could not read backup directory: ${
collapseWhitespace(message) || "unknown error"
}`,
);
@ndycode
Copy link
Owner Author

ndycode commented Mar 15, 2026

Follow-up pushed on the current PR head (6605d4d).

Changes in this pass:

  • clarified restore assessment/import counter semantics around deduplicated current state
  • documented skipped semantics for metadata-refresh restores
  • reran focused validation:
    • npx eslint lib/storage.ts lib/codex-manager.ts test/storage.test.ts test/codex-manager-cli.test.ts
    • npx vitest run test/storage.test.ts test/codex-manager-cli.test.ts --maxWorkers=1

@greptile review
@cubic review
@coderabbitai full review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 15, 2026

Follow-up pushed on the current PR head (6605d4d).

Changes in this pass:

  • clarified restore assessment/import counter semantics around deduplicated current state
  • documented skipped semantics for metadata-refresh restores
    ...

@ndycode I have started the AI code review. It will take a few minutes to complete.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

@ndycode — acknowledged. triggering a full review of pr #76 at head 6605d4d. scope will cover:

  • lib/storage.ts — updated skipped semantics for metadata-refresh restores, deduplicated counter logic in importAccounts, triple-negation in findExistingNamedBackupPath, swallowed validation error in resolveNamedBackupRestorePath catch block, retryTransientFilesystemOperation eperm/ebusy/eagain coverage, symlink rejection, toctou re-assessment guards, and the storagePath transaction guard in exportAccounts
  • lib/codex-manager.ts — clarified assessment/import counter semantics in runBackupRestoreManager, re-assessment-before-confirm flow, error containment back to login menu, and formatRelativeDateShort edge cases
  • lib/cli.tsrestore-backup mode variant and fallback alias wiring for "u"/"backup"/"restore"
  • lib/ui/auth-menu.ts / lib/ui/copy.ts — recovery section ui and copy string additions
  • test/storage.test.ts / test/codex-manager-cli.test.ts — concurrency regression, windows eperm retry, uppercase .JSON restore, toctou delete-after-assessment, partial assessment failure, symlink rejection, re-assessment freshness, zero-account recovery paths, and coverage for the newly documented skipped/counter semantics
✅ Actions performed

Full review triggered.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/codex-manager.ts">

<violation number="1" location="lib/codex-manager.ts:4474">
P2: CLI `restore-backup` command falsely exits with a success code (0) when the restore fails.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@ndycode
Copy link
Owner Author

ndycode commented Mar 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ndycode
Copy link
Owner Author

ndycode commented Mar 15, 2026

@coderabbitai review
@greptile review
@cubic-dev-ai review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 15, 2026

@coderabbitai review
@greptile review
@cubic-dev-ai review

@ndycode I have started the AI code review. It will take a few minutes to complete.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

@ndycode — triggering a review of pr #76 now. will cover:

  • lib/storage.ts — triple-negation in findExistingNamedBackupPath, swallowed validation error in resolveNamedBackupRestorePath catch block, retryTransientFilesystemOperation eperm/ebusy/eagain coverage, symlink rejection, toctou re-assessment guards, storagePath transaction guard in exportAccounts, and updated skipped semantics for metadata-refresh restores
  • lib/codex-manager.tsrunBackupRestoreManager fan-out concurrency, re-assessment-before-confirm flow, error containment back to login menu, formatRelativeDateShort edge cases (midnight rollover, locale), and the refactored runAuthLogin loop
  • lib/cli.tsrestore-backup mode variant and fallback alias wiring for "u"/"backup"/"restore"
  • lib/ui/auth-menu.ts / lib/ui/copy.ts — recovery section ui injection and copy string additions
  • test/storage.test.ts / test/codex-manager-cli.test.ts — concurrency regression, windows eperm retry, uppercase .JSON restore, toctou delete-after-assessment, partial assessment failure, symlink rejection, re-assessment freshness, and zero-account recovery paths
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 11 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
test/storage.test.ts (1)

1587-1596: ⚠️ Potential issue | 🟠 Major

add a direct restore regression for the fallback-miss path.

at test/storage.test.ts:1587 (Line [1587]), this only pins resolveNamedBackupRestorePath(...) + importAccounts(...). it does not pin restoreNamedBackup(...) when the requested name is absent from listings and the fallback path is used. please add a deterministic case that calls restoreNamedBackup(" missing-backup "), asserts /Import file not found/, and confirms active storage is unchanged.

proposed test addition
+		it("throws file-not-found for never-present named backups and leaves storage unchanged", async () => {
+			await clearAccounts();
+			const before = await loadAccounts();
+			await expect(restoreNamedBackup("  missing-backup  ")).rejects.toThrow(
+				/Import file not found/,
+			);
+			expect(await loadAccounts()).toEqual(before);
+		});

As per coding guidelines, test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/storage.test.ts` around lines 1587 - 1596, Add a deterministic
regression test that calls restoreNamedBackup("  missing-backup  "), expects it
to reject with /Import file not found/, and verifies active storage was not
modified; specifically, after calling restoreNamedBackup("  missing-backup  ")
assert it rejects toThrow(/Import file not found/) and then confirm active
storage remains the same (e.g., compare a pre-call snapshot obtained via the
same accessor used elsewhere in tests such as listing or reading current
accounts) — use resolveNamedBackupRestorePath/requestedName trimming behavior
and the existing importAccounts/buildNamedBackupPath helpers to mirror the
fallback-miss scenario.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/codex-manager.ts`:
- Around line 4477-4488: The restore-backup branch fails to clear a previously
set storage scope; call setStoragePath(null) before invoking
runBackupRestoreManager in the command === "restore-backup" block so lingering
storage scope won't point the restore at the wrong directory/account, and add a
vitest in test/codex-manager-cli.test.ts that seeds a scoped storage path then
invokes runCodexMultiAuthCli(["auth","restore-backup"]) to assert the restore
uses the cleared scope; reference runBackupRestoreManager, setStoragePath, and
runCodexMultiAuthCli when locating and updating the code and test.
- Around line 4255-4338: The loop that calls assessNamedBackupRestore can skip
all backups and leave assessments empty, which yields a back-only picker and
allows the restore command to incorrectly return success; after the
Promise.allSettled processing and before building items, detect if
assessments.length === 0, consolidate the non-containment rejection reasons from
settledAssessments (use collapseWhitespace to normalize messages and fall back
to a single "all assessments failed" message), log or surface that consolidated
error, and return false to indicate failure; reference the variables/idents
assessNamedBackupRestore, settledAssessments, isNamedBackupContainmentError,
collapseWhitespace, assessments, select and backupDir when locating where to add
this check, and update the corresponding test test/codex-manager-cli.test.ts to
cover the all-failed case.

In `@lib/storage.ts`:
- Around line 3079-3088: The code does a check-then-read race: after using
existsSync(resolvedPath) the file may be deleted before
retryTransientFilesystemOperation(() => fs.readFile(resolvedPath, "utf-8")),
causing a raw ENOENT to bubble up; wrap the read in a try/catch inside the retry
(or immediately around the awaited call) and if you see an ENOENT map/throw a
new Error(`Import file not found: ${resolvedPath}`) so the higher-level import
contract is preserved; update/ add vitest cases in test/storage.test.ts and
test/codex-manager-cli.test.ts that delete the file between existsSync and read
to assert the translated error, and ensure you reference resolvedPath,
existsSync, retryTransientFilesystemOperation, and fs.readFile when editing.

---

Duplicate comments:
In `@test/storage.test.ts`:
- Around line 1587-1596: Add a deterministic regression test that calls
restoreNamedBackup("  missing-backup  "), expects it to reject with /Import file
not found/, and verifies active storage was not modified; specifically, after
calling restoreNamedBackup("  missing-backup  ") assert it rejects
toThrow(/Import file not found/) and then confirm active storage remains the
same (e.g., compare a pre-call snapshot obtained via the same accessor used
elsewhere in tests such as listing or reading current accounts) — use
resolveNamedBackupRestorePath/requestedName trimming behavior and the existing
importAccounts/buildNamedBackupPath helpers to mirror the fallback-miss
scenario.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b9d0c172-8bd4-4e83-b086-0379a3875eee

📥 Commits

Reviewing files that changed from the base of the PR and between 8d61d29 and 61a466b.

📒 Files selected for processing (7)
  • docs/reference/commands.md
  • docs/reference/public-api.md
  • docs/reference/storage-paths.md
  • lib/codex-manager.ts
  • lib/storage.ts
  • test/codex-manager-cli.test.ts
  • test/storage.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Greptile Review
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
docs/**

⚙️ CodeRabbit configuration file

keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.

Files:

  • docs/reference/storage-paths.md
  • docs/reference/commands.md
  • docs/reference/public-api.md
test/**

⚙️ CodeRabbit configuration file

tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.

Files:

  • test/codex-manager-cli.test.ts
  • test/storage.test.ts
lib/**

⚙️ CodeRabbit configuration file

focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.

Files:

  • lib/codex-manager.ts
  • lib/storage.ts
🔇 Additional comments (12)
test/codex-manager-cli.test.ts (11)

558-562: mock correctly chains internal storage calls per lib/storage.ts:1889-1901.

the restoreAssessedNamedBackupMock implementation at lines 558-562 mirrors the real restoreAssessedNamedBackup behavior: it calls resolveNamedBackupRestorePath first, then importAccounts. this ensures tests exercising path validation failures (like test/codex-manager-cli.test.ts:2630) surface correctly when resolveNamedBackupRestorePathMock rejects.

one note: the mock doesn't check eligibleForRestore like the real implementation does at lib/storage.ts:1892-1896. this is fine since production code (lib/codex-manager.ts:4307 per pr context) checks eligibility before calling restoreAssessedNamedBackup, and tests at lines 3477-3572 verify this flow.


3216-3291: solid concurrency regression test for assessment cap.

this test properly validates the NAMED_BACKUP_ASSESS_CONCURRENCY bound by:

  1. importing the actual constant at test/codex-manager-cli.test.ts:3219-3222 via vi.importActual
  2. using deferred promises with microtask scheduling at lines 3241-3257 to control release timing
  3. tracking maxInFlight and asserting it stays within bounds at lines 3288-3290

the deferred-gate pattern ensures deterministic timing without relying on sleep or wall-clock delays.


3574-3660: good regression for reassessment EBUSY rejection path.

test at test/codex-manager-cli.test.ts:3574-3660 covers the case where re-assessment fails with EBUSY before confirmation. this exercises the error path at lib/codex-manager.ts:4307 per pr context and verifies:

  • confirmMock not called (line 3652)
  • importAccountsMock not called (line 3653)
  • error message logged (line 3654-3656)
  • login menu re-entered (line 3633)

this addresses the past review comment about reassessment rejection regression.


5095-5183: good quota refresh ordering regression for backup restore.

this test at test/codex-manager-cli.test.ts:5095-5183 verifies that in-flight menu quota refresh completes before listNamedBackups is called. uses deferred promises at lines 5142-5153 to gate the fetch, then asserts ordering at lines 5176-5179 via invocationCallOrder. this covers a potential race where backup restore could start before quota cache is persisted.


3702-3794: deterministic timestamp formatting test.

the test at test/codex-manager-cli.test.ts:3702-3794 fixes timestamp to Date.UTC(2026, 0, 10, 12, 0, 0) via vi.spyOn(Date, "now") at line 3706. this avoids locale-dependent failures that would occur with toLocaleDateString(). the assertions at lines 3787-3790 check relative time labels ("today", "yesterday", "3d ago") rather than formatted date strings.

this addresses the past review comment about avoiding exact localized date string matching.


2730-2795: decline path regression covers menu re-entry.

test at test/codex-manager-cli.test.ts:2730-2795 sets confirmMock.mockResolvedValueOnce(false) at line 2773 and asserts:

  • promptLoginModeMock called twice (line 2793) - verifies loop back to menu
  • importAccountsMock not called (line 2794) - verifies no restore attempted

this addresses the past review comment about asserting the decline path loops back to login.


3011-3041: manager-level EPERM regression for backup discovery.

test at test/codex-manager-cli.test.ts:3011-3041 uses makeErrnoError("EPERM...", "EPERM") at lines 3014-3017 to simulate windows-style permission failure on listNamedBackups. verifies:

  • assessNamedBackupRestoreMock not called (line 3030)
  • error message contains EPERM (lines 3033-3037)
  • login menu re-entered (line 3029)

this addresses the past review comment about manager-level EPERM regression for backup discovery.


2389-2455: login recovery restore flow well-tested.

test at test/codex-manager-cli.test.ts:2389-2455 exercises the happy path for restore via login recovery menu:

  • listNamedBackupsMock called (line 2441)
  • assessNamedBackupRestoreMock called with correct args (lines 2442-2449)
  • confirmMock called once (line 2450)
  • importAccountsMock called with resolved path (lines 2451-2453)
  • setCodexCliActiveSelectionMock called to sync active account (line 2454)

assertions cover the full restore-and-sync flow.


2587-2653: path containment regression covers TOCTOU scenario.

test at test/codex-manager-cli.test.ts:2587-2653 simulates backup root changing between assessment and final import by making resolveNamedBackupRestorePathMock reject at line 2630-2632 with "Backup path escapes backup directory". verifies:

  • confirmMock was called (line 2644) - user already confirmed before path revalidation
  • importAccountsMock not called (line 2645) - blocked by path validation
  • error logged (lines 2647-2649)
  • login menu re-entered (line 2646)

this covers the TOCTOU re-assessment guard per pr objectives.


3148-3214: partial assessment failure regression with warn logging.

test at test/codex-manager-cli.test.ts:3148-3214 covers mixed assessment results where one backup fails and one succeeds:

  • assessNamedBackupRestoreMock throws for "broken-backup" at lines 3182-3184
  • healthy backup remains selectable (lines 3191-3194)
  • warning logged for skipped backup (lines 3206-3210)
  • restore proceeds for healthy backup (lines 3203-3205)

good coverage for partial failure resilience in the restore menu.


1-10: mock scaffold well-structured for named-backup flows.

the mock additions provide comprehensive coverage:

  • MOCK_BACKUP_DIR and mockBackupPath at lines 5-9 create consistent test paths
  • storage mocks at lines 120-125 intercept all named-backup apis
  • confirmMock at lines 209-213 enables confirmation flow testing
  • beforeEach setup at lines 519-563 provides sensible defaults while allowing per-test overrides

the layered mock approach keeps individual tests focused on their specific scenarios.

Also applies to: 17-22, 107-129, 209-214, 519-563

test/storage.test.ts (1)

3116-3168: nice limiter regression coverage here.

test/storage.test.ts:3116 uses NAMED_BACKUP_LIST_CONCURRENCY to scale workload and asserts peak read concurrency stays bounded. this is a strong, deterministic vitest regression for the queue-throttling contract.

@ndycode
Copy link
Owner Author

ndycode commented Mar 15, 2026

Follow-up pushed on the current PR head (ef05872).

Changes in this pass:

  • fail fast when every named-backup assessment rejects so direct auth restore-backup returns non-zero instead of falling through a back-only picker
  • clear stale storage scope before the direct restore command runs
  • preserve the friendly Import file not found: ... contract when a backup disappears after the exists/read race
  • tighten the direct-restore test coverage and storage regression coverage for those paths

Validation rerun:

  • npx eslint lib/storage.ts lib/codex-manager.ts test/storage.test.ts test/codex-manager-cli.test.ts
  • npx vitest run test/storage.test.ts test/codex-manager-cli.test.ts --maxWorkers=1

@coderabbitai review
@greptile review
@cubic-dev-ai review

@cubic-dev-ai
Copy link

cubic-dev-ai bot commented Mar 15, 2026

Follow-up pushed on the current PR head (ef05872).

Changes in this pass:

  • fail fast when every named-backup assessment rejects so direct auth restore-backup returns non-zero instead of falling through a back-only picker
  • clear stale storage scope before the direct restore command runs
    ...

@ndycode I have started the AI code review. It will take a few minutes to complete.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

@ndycode — acknowledged. triggering a review of pr #76 at head ef05872. scope will cover:

  • lib/codex-manager.ts — fail-fast branch when all named-backup assessments reject (direct auth restore-backup must return non-zero), stale storage scope clear before the direct restore command runs, and the re-assessment-before-confirm flow
  • lib/storage.ts — preserved "Import file not found: ..." contract in resolveNamedBackupRestorePath for the post-exists/read race (backup disappears between lstat check and actual read), symlink rejection, toctou guards, and retryTransientFilesystemOperation eperm/ebusy/eagain coverage
  • test/codex-manager-cli.test.ts — direct-restore fail-fast path, all-assessments-reject scenario, exit-code assertion for non-zero return
  • test/storage.test.ts — regression coverage for the disappear-after-exists race and the friendly error message contract
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ndycode
Copy link
Owner Author

ndycode commented Mar 15, 2026

PR body reset to the concise current summary for head ef05872.

@greptile review
@cubic-dev-ai review
@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

@ndycode — acknowledged. triggering a review of pr #76 at head ef05872. scope will cover:

  • lib/codex-manager.ts — fail-fast branch when all named-backup assessments reject (direct auth restore-backup must return non-zero), stale storage scope clear before direct restore command, re-assessment-before-confirm flow, and runBackupRestoreManager error containment back to login menu
  • lib/storage.ts — preserved "Import file not found: ..." contract in resolveNamedBackupRestorePath for the post-exists/read race, symlink rejection, toctou re-assessment guards, retryTransientFilesystemOperation eperm/ebusy/eagain coverage, and storagePath transaction guard in exportAccounts
  • lib/cli.tsrestore-backup mode variant and fallback alias wiring for "u"/"backup"/"restore"
  • lib/ui/auth-menu.ts / lib/ui/copy.ts — recovery section ui injection and copy string additions
  • test/codex-manager-cli.test.ts — direct-restore fail-fast path, all-assessments-reject scenario, exit-code assertion for non-zero return
  • test/storage.test.ts — regression coverage for the disappear-after-exists race and the friendly error message contract
✅ Actions performed

Full review triggered.

@ndycode ndycode closed this Mar 15, 2026
@ndycode ndycode deleted the git-plan/02-backup-restore-manager branch March 15, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants